package net.csdn.business.auth.service.impl;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.csdn.business.auth.service.PersonTokenService;
import net.csdn.business.common.domain.request.oauth.PersonalAccessTokenDTO;
import net.csdn.business.common.domain.request.oauth.PersonalAccessTokenQuery;
import net.csdn.business.common.domain.request.oauth.RevokedPersonTokenDTO;
import net.csdn.business.common.domain.vo.oauth.OauthTokenInfoVO;
import net.csdn.business.common.domain.vo.oauth.PersonalAccessTokenVO;
import net.csdn.business.common.enums.ResultCodeEnum;
import net.csdn.business.common.exception.BusinessException;
import net.csdn.business.common.oauth2.component.CustomRedisTokenStore;
import net.csdn.business.common.oauth2.component.PersonAuthenticationToken;
import net.csdn.business.common.oauth2.model.LoginUser;
import net.csdn.business.common.oauth2.service.IPersonalAccessTokenService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.util.*;

/**
 * @Auther: zhangyalei
 * @Date: 2022/12/12 13:49
 * @Description:
 */
@Slf4j
@Service
public class PersonTokenServiceImpl implements PersonTokenService {

    @Autowired
    private  CustomRedisTokenStore tokenStore;

    @Autowired
    private IPersonalAccessTokenService personalAccessTokenService;

    @Autowired
    @Qualifier("getResourceServerTokenServices")
    private ResourceServerTokenServices tokenServices;

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor;


    @Override
    public Page<PersonalAccessTokenVO> findPageList(PersonalAccessTokenQuery query) {
        return personalAccessTokenService.findPageList(query);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PersonalAccessTokenVO addPersonalAccess(PersonalAccessTokenDTO personToken) {
        log.info("PersonTokenFeign_addPersonToken 请求参数为：{}",JSON.toJSONString(personToken));
        //保存成功
        PersonalAccessTokenVO result=personalAccessTokenService.insertPersonTokenAndPermission(personToken);
        //远程调用认证服务，生成私人token
        if(result!=null){
            Set<String> scopes =null;
            String scopesStr=personToken.getScopes();
            if(StringUtils.isNotBlank(scopesStr)){
                String [] scopeArray=scopesStr.split(",");
                scopes = new HashSet<String>(Arrays.asList(scopeArray));
            }
            String clientId = "system";
            //设备id,传此参数是为了让同一账号在多个浏览器登录时产生不通的token。
            String deviceId= IdUtil.simpleUUID();
            LoginUser loginUser=new LoginUser(personToken.getUserId(),personToken.getUserName(),personToken.getNickName(),personToken.getEmail(),deviceId,
                    personToken.getUserId(), true, true, true,
                    true, AuthorityUtils.NO_AUTHORITIES);
            Authentication personAuthentication=new PersonAuthenticationToken(loginUser,loginUser.getAuthorities());
            TokenRequest tokenRequest = new TokenRequest(MapUtil.newHashMap(), clientId, scopes,
                    "person");
            HashMap<String, String> modifiable = new HashMap<String, String>();
            // Add grant type so it can be retrieved from OAuth2Request
            modifiable.put("grant_type", "person");
            OAuth2Request oAuth2Request = new OAuth2Request(modifiable, clientId, null, true, scopes,
                    null, null, null, null);
            OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, personAuthentication);
            //验证token是否已经存在,存在，则删除，重新生成
            OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(oAuth2Authentication);
            if (existingAccessToken != null) {
                tokenStore.removeAccessToken(existingAccessToken);
            }
            //生成token,并存入数据库
            DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(result.getTokenId());
            Date expirTime = personToken.getExpireTime();
            if (expirTime !=null) {
                accessToken.setExpiration(expirTime);
            }
            //token.setRefreshToken(refreshToken);
            accessToken.setScope(oAuth2Authentication.getOAuth2Request().getScope());
            tokenStore.storeAccessToken(accessToken, oAuth2Authentication);
            log.info("生成私人token信息为：{}", JSON.toJSONString(accessToken));
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer revokedPersonToken(RevokedPersonTokenDTO params) {
        log.info("PersonTokenFeign_revokedPersonToken 请求参数为：{}",JSON.toJSONString(params));
        String userId=params.getUserId();
        String id=params.getId();
        //查询token
        PersonalAccessTokenVO personalAccessTokenVO=personalAccessTokenService.queryById(id);
        if(personalAccessTokenVO==null){
            throw new BusinessException(ResultCodeEnum.PARAMETER_ERROR);
        }
        if(!userId.equals(personalAccessTokenVO.getUserId())){
            throw new BusinessException(ResultCodeEnum.PARAMETER_ERROR);
        }
        String tokenId=personalAccessTokenVO.getTokenId();
        int result=personalAccessTokenService.revoked(tokenId);
        //远程调用认证服务器删除token数据
        if(result>0){
            //根据tokenId读取token数据
            OAuth2AccessToken oAuth2AccessToken=tokenStore.readAccessToken(tokenId);
            if(oAuth2AccessToken!=null){
                //删除token数据
                tokenStore.removeAccessToken(oAuth2AccessToken);
            }
        }
        return 1;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer revokedAllPersonToken(RevokedPersonTokenDTO params) {
        log.info("PersonTokenFeign_revokedAllPersonToken 请求参数为：{}",JSON.toJSONString(params));
        String userId=params.getUserId();
        //根据userId获取tokenId列表
        PersonalAccessTokenQuery query=new PersonalAccessTokenQuery();
        query.setUserId(userId);
        List<PersonalAccessTokenVO> list=personalAccessTokenService.findList(query);
        if(list==null||list.size()==0){
            return 0;
        }
        int result=personalAccessTokenService.revokedAll(userId);
        //远程调用认证服务器删除token数据，此处需要异步执行
        if(result>0){
            //异步循环调用token认证服务器删除token数据
            taskExecutor.execute(()->{
                list.stream().map(tokenVO->tokenVO.getTokenId()).forEach(tokenId->{
                    //根据tokenId读取token数据
                    OAuth2AccessToken oAuth2AccessToken=tokenStore.readAccessToken(tokenId);
                    if(oAuth2AccessToken!=null){
                        //删除token数据
                        tokenStore.removeAccessToken(oAuth2AccessToken);
                    }
                });
            });
        }
        return result;
    }

    @Override
    public OauthTokenInfoVO getInfo(String token) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("authenticationManager.authenticate");
        OAuth2Authentication oAuth2Authentication = tokenServices.loadAuthentication(token);
        if (oAuth2Authentication == null) {
            throw new InvalidTokenException("Invalid token: " + token);
        }
        OauthTokenInfoVO result=new OauthTokenInfoVO();
        LoginUser loginUser=(LoginUser)oAuth2Authentication.getPrincipal();
        result.setTokenId(token);
        result.setUserId(loginUser.getId());
        result.setUsername(loginUser.getUsername());
        OAuth2Request oAuth2Request=oAuth2Authentication.getOAuth2Request();
        Set<String> scopes=oAuth2Request.getScope();
        result.setScopes(scopes);
        stopWatch.stop();
        log.info("当前任务名：{},耗时：{}ms",stopWatch.getLastTaskName(),stopWatch.getLastTaskTimeMillis());
        return result;
    }
}
